PromQL: Wire WITHOUT in Translator#144261
Conversation
|
Pinging @elastic/es-storage-engine (Team:StorageEngine) |
felixbarny
left a comment
There was a problem hiding this comment.
I'm not though the PR yet but some early comments here. This PR is now also quite large again.
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
| // For WITHOUT the concrete labels are only determined during translation (they depend on which labels | ||
| // the data actually has minus the excluded set), so we must resolve them against the translated plan. | ||
| if (result.hasExcludedLabels()) { | ||
| plan = new Project(promqlCommand.source(), plan, resolveOutput(promqlCommand, plan, result)); |
There was a problem hiding this comment.
Resolution is an overloaded term here. The analyzer has a resolution phase where it resolves tables/functions/attributes etc. We should probably use a different term here.
| return Literal.timeDuration(promqlCommand.source(), Duration.ofMillis(Math.max(1L, nextRoundedValue - roundedStart))); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
For most of these helper methods, there's potential to simplify by using the streams API rather than manual/imperative loops and filtering.
| projections.add(stepAttr); | ||
|
|
||
| return new Aggregate(promqlCommand.source(), plan, groupings, aggs); | ||
| List<Attribute> expectedExtra = promqlCommand.output().subList(2, promqlCommand.output().size()); |
There was a problem hiding this comment.
expectedExtra sounds a bit cryptic. These are the dimension columns, right? Maybe add a short method-level comment that describes the column layout like [value, step, ...dimensions].
| return new PackedGrouping(new Project(ctx.promqlCommand().source(), rewritten, projections), packedAttr); | ||
| } | ||
|
|
||
| private static List<NamedExpression> resolveOutput(PromqlCommand promqlCommand, LogicalPlan plan, TranslationResult result) { |
There was a problem hiding this comment.
A high level description of what this does and what the purpose is would be useful.
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
| assertThat(tsAggregate.timeBucket().buckets().fold(FoldContext.small()), equalTo(Duration.ofMinutes(10))); | ||
| } | ||
|
|
||
| public void testWithoutGroupingProducesTimeSeriesOutput() { |
There was a problem hiding this comment.
These tests can be removed now, right? They live in PromqlPlanWithoutGroupingTests now.
felixbarny
left a comment
There was a problem hiding this comment.
The logic is sound but the code reads as a sequence of "what" rather than "why" and I found it hard to follow, even though I have an understanding what this is trying to do at a high level. The biggest single improvement would be a concise top-level explanation of the label-propagation model, followed by splitting createOuterAggregate into its two distinct cases. Maybe there are more things we can do to make it more readable.
| @@ -217,20 +267,56 @@ private TranslationResult translateNode(LogicalPlan node, LogicalPlan currentPla | |||
| * expressions embedded inside the aggregation, but do not create Aggregate plan nodes themselves. | |||
| */ | |||
| private TranslationResult translateAcrossSeriesAggregate(AcrossSeriesAggregate agg, LogicalPlan currentPlan, TranslationContext ctx) { | |||
There was a problem hiding this comment.
The whole PR is built around a bidirectional label-propagation scheme — requiredLabels flows down, exposedLabels/excludedLabels flow up — but this model is never stated explicitly. A developer encountering this code cold has to reverse-engineer it from the individual pieces. A single Javadoc comment at the top of translateAcrossSeriesAggregate explaining the two-pass propagation strategy and why it's needed for WITHOUT would be worth more than all the inline comments combined.
There was a problem hiding this comment.
Good suggestion. Added doc section for that.
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
...a/org/elasticsearch/xpack/esql/optimizer/rules/logical/promql/TranslatePromqlToEsqlPlan.java
Show resolved
Hide resolved
| } | ||
| } | ||
|
|
||
| // TODO: Should we fail in case of missmatch? |
There was a problem hiding this comment.
| // TODO: Should we fail in case of missmatch? | |
| // TODO: Should we fail in case of mismatch? |
In what cases does a mismatch happen? Can we validate this early and throw a validation error (4xx) rather than failing here (5xx)?
There was a problem hiding this comment.
We should probably surface this in some form, but it is not straightforward as far as I can tell because PromQL translation happens after analysis. For example, in:
avg by (c) (
sum without (b) (
max by (a,b) (...)
)
)
we can tell that the result is meaningless because c cannot appear in the output of the without stage since innermost doesn't produce it. This is likely typo and warning would be a good UX (not necessary fail query if we want to stay compliant with PromQL spec).
There was a problem hiding this comment.
Let's track an issue for this. Also remember that 5xx will cause serverless alerts.
| resolved.add(byName); | ||
| } | ||
| } | ||
| return normalizeLabels(resolved); |
There was a problem hiding this comment.
Nit: Some callers pass in the output of prior `normalizeLabels(...) calls. This is low overhead but slightly redundant.
| /** | ||
| * Strips non-grouping attributes (metadata, unresolved, metrics, NULLs) and deduplicates by field name. | ||
| */ | ||
| private static List<Attribute> normalizeLabels(List<Attribute> attributes) { |
There was a problem hiding this comment.
Calling this for agg.output() seems redundant as nulls should be filtered out already. For agg.groupings() it seems we don't need the full normalization but just the null filtering?
The only place where all of the normalization is needed is normalizeLabels(resolved) inside resolveLabels(). Maybe also rename to something less vague than "normalize". Maybe something like retainLabelAttributes.
Thanks for the feedback @felixbarny. I agree, we may want to add diagrams and examples to illustrate label propagation model. I took a pass and added one high level summary above |
| } | ||
| } | ||
|
|
||
| // TODO: Should we fail in case of missmatch? |
There was a problem hiding this comment.
Let's track an issue for this. Also remember that 5xx will cause serverless alerts.
Stage the capability flag, lowering prep, and gated coverage needed for incremental WITHOUT support while keeping translator behavior unchanged.
Wire WITHOUT grouping through selector translation, aggregate planning, and root projection so the staged plumbing becomes functional. Enable the capability and focused verifier/optimizer coverage for the supported WITHOUT cases.
Upstream moved STEP_COLUMN_NAME to PromqlCommand; use the accessor.
2350951 to
4fd76d4
Compare
|
@sidosera It doesn't look like Kibana Security owns any changes here though we were triggered as code owner. I will remove the request for our team, but I am still happy to review if there is something you'd like us to look at. |
|
Closing this as future refactoring PR landed containing this change. |
Pull request was closed
Adds the translation flow for PromQL
withoutgrouping as part of #139793.Summary:
WITHOUTthrough translator pipline (selector translation, aggregate planning, etc)Stack: